using System;
using System.Reflection;
using DevExpress.Xpo;
using PostSharp.Laos;

namespace Atlantic.XpoAutomaticProperties
{
   /// <summary>
   /// Implementation of <see cref="OnMethodBoundaryAspect"/> that initialised 
   /// <see cref="XPCollection"/>  and <see cref="XPCollection"/> properties.
   /// </summary>
   [Serializable]
   internal class CollectionCreatorSubAspect : OnFieldAccessAspect
   {
      #region Constructors

      /// <summary>
      /// Initializes a new <see cref="PropertyChangedNotifierSubAspect"/>.
      /// <para>
      /// 		<see cref="FieldAccessEventArgs.FieldInfo"/> could be used instead of passing
      /// <paramref name="propertyName"/> and <see cref="propertyType"/>.
      /// </para>
      /// </summary>
      /// <param name="parent">Parent <see cref="XpoAutomaticPropertiesAttribute"/>.</param>
      /// <param name="propertyName">Name of the collection property.</param>
      /// <param name="propertyType">Type of the collection property.</param>
      /// <param name="backingField">The backing field.</param>
      public CollectionCreatorSubAspect(XpoAutomaticPropertiesAttribute parent, string propertyName, Type propertyType, FieldInfo backingField)
      {
         this.propertyType = propertyType;
         this.propertyName = propertyName;
         this.backingField = backingField;
         AspectPriority = parent.AspectPriority;
      }

      #endregion

      #region Fields

      private readonly string propertyName;
      private readonly Type propertyType;
      private readonly FieldInfo backingField;

      #endregion

      #region Methods

      /// <summary>
      /// Method called instead of the <i>get</i> operation on the modified field.
      /// </summary>
      /// <param name="eventArgs">Event arguments specifying which field is being
      /// accessed and which is its current value.</param>
      public override void OnGetValue(FieldAccessEventArgs eventArgs)
      {
         base.OnGetValue(eventArgs);
         if (eventArgs.StoredFieldValue != null) return;

         var instance = (XPBaseObject) eventArgs.Instance;
         var newCollection = Activator.CreateInstance(propertyType, instance.Session, instance,
                                                  instance.ClassInfo.GetMember(propertyName));

         // PostSharp makes it really easy to set the field value...all we need to do is to set a property in the args...
         // But this on its own doesn't work for many-many associations.  XPO's ReflectionPropertyInfo does some IL 
         // generation around getting values and this gives a stack overflow exception. So, we use reflection to 
         // set the field value.
         eventArgs.ExposedFieldValue = newCollection;
         backingField.SetValue(instance, newCollection, BindingFlags.NonPublic | BindingFlags.SetField, null, null);

         //eventArgs.ExposedFieldValue = collection;
      }

      #endregion
   }
}